/*
 * Marmoset Add-In for Microsoft Visual Studio
 *
 * Copyright (C) 2007, David H. Hovemeyer
 * Copyright (C) 2007, Fidel K. Estrella
 *
 * This program is free software licensed under the
 * terms of the GNU General Public License, version 2.
 * See the file "COPYING.txt" for details.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Text;
using System.Windows.Forms;
using Extensibility;
using EnvDTE;
using EnvDTE80;

namespace Marmoset
{
    #region Certificates Policy
    /// <summary>
    /// <para>
    /// This class downloaded from http://weblogs.asp.net/jan/archive/2003/12/04/41154.aspx
    /// </para>
    /// <para>
    /// Most marmoset installations will be using a self-signed (untrusted)
    /// SSL certificate, so we need to hack things so that
    /// .NET will still be able to communicate over https.
    /// </para>
    /// </summary>
    public class TrustAllCertificatesPolicy : System.Net.ICertificatePolicy
    {
        public TrustAllCertificatesPolicy()
        {
        }

        public bool CheckValidationResult(ServicePoint sp,
         X509Certificate cert, WebRequest req, int problem)
        {
            return true;
        }
    }
    #endregion

    /// <summary>
    /// The Submitter class automates exporting the solution to
    /// a temporary zipfile. prompting the user for username/password,
    /// and uploading the zipfile to the Marmoset server.
    /// </summary>
    class Submitter : Exporter
    {
        #region Read-Only Strings for the Submit.Properties file
        private static readonly string SUBMIT_PROPERTIES_FILE = "submit.properties";

        private static readonly string[] REQUIRED_SUBMIT_PROPERTIES =
            { "submitURL", "courseName", "semester", "projectNumber" };

        private static readonly string ERROR_DIALOG_TITLE = "Submit error";
        #endregion

        #region Private Fields
        private Properties _submitProperties;
        private Uri _uri;
        private Hashtable _requestParams;
        private UploadSpec _uploadSpec;
        private HttpWebResponse _response;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="solution">Solution to submit</param>
        public Submitter(Solution solution)
            : base(solution)
        {
        }
        #endregion

        #region Submit Method
        /// <summary>
        /// Submit the Solution to the Marmoset server.
        /// Information about the server URL,
        /// project information, etc. are taken
        /// from the submit.properties file in the same
        /// directory as the Solution file.
        /// </summary>
        public void Submit()
        {
            string solutionDir = Path.GetDirectoryName(_solution.FullName);

            if ((_submitProperties = ReadProperties(solutionDir, SUBMIT_PROPERTIES_FILE, REQUIRED_SUBMIT_PROPERTIES)) == null)
            {
                return;
            }

            //MessageBox.Show("OK, submission looks good so far...");

            // Use the submit form to prompt the user for his/her
            // username and password.
            SubmitForm submitForm = new SubmitForm();
            string userName = _submitProperties.Get("campusUID");
            submitForm.SetUsername(userName != null ? userName : "");
            submitForm.SetServer(_submitProperties.Get("submitURL"));
            if (submitForm.ShowDialog() != DialogResult.OK)
            {
                // Submit cancelled by user
                return;
            }

            userName = submitForm.GetUsername();
            string password = submitForm.GetPassword();

            if (userName == "")
            {
                Dialogs.ShowErrorDialog(ERROR_DIALOG_TITLE, "User name field must not be blank.");
                return;
            }
            if (password == "")
            {
                Dialogs.ShowErrorDialog(ERROR_DIALOG_TITLE, "Password field must not be blank.");
                return;
            }

            string submitURL = _submitProperties.Get("submitURL");
            _submitProperties.Remove("submitURL"); // not passed as a request param
            _submitProperties.Set("campusUID", userName);
            _submitProperties.Set("password", password);

            // Fill in a few additional params that the Marmoset server just can't live without
            _submitProperties.Set("submitClientTool", VersionInfo.SUBMIT_CLIENT_TOOL_NAME);
            _submitProperties.Set("submitClientVersion", VersionInfo.SUBMIT_CLIENT_VERSION);
            
            // Get number of milliseconds since the Unix epoch
            TimeSpan span = DateTime.Now - new DateTime(1970, 1, 1);
            long millisSinceEpoch = (long) span.TotalMilliseconds;
            _submitProperties.Set("cvstagTimestamp", "t" + millisSinceEpoch);

            _uri = new Uri(submitURL);

            WriteToOutput("Attempting to submit solution to " + _uri.ToString());

            string tempFileName = Path.GetTempFileName();
            try
            {
                // Export the solution to a temp file
                ExportToTempFile(tempFileName);
                WriteToOutput("Solution exported to temporary file " + tempFileName);

                // Idea for setting the certifcate policy taken from:
                //   http://www.supinfo-projects.com/en/2005/webserviceshttpsen/2/

                System.Net.ServicePointManager.CertificatePolicy =
                    new TrustAllCertificatesPolicy(); 

                // Configure POST parameters
                _requestParams = new Hashtable();
                foreach (string key in _submitProperties.GetKeys())
                {
                    string value = _submitProperties.Get(key);
                    _requestParams.Add(key, value);

                    //WriteToOutput("  adding request param " + key + "=" + value);
                }

                // Set the file upload spec
                _uploadSpec = new UploadSpec(tempFileName, "submittedFiles");

                // Do the upload to the server!!!
                WriteToOutput("Starting the upload...");

                // Upload the solution to the server!
                UploadToServer();

                WriteToOutput("Upload returned");

                if (_response.StatusCode == HttpStatusCode.OK)
                {
                    Dialogs.ShowInfoDialog("Submit result", "Project submitted successfully!");
                }
                else
                {
                    Dialogs.ShowErrorDialog(ERROR_DIALOG_TITLE, "Error submitting project: " + _response.StatusCode.ToString());
                }
            }
            finally
            {
                try
                {
                    File.Delete(tempFileName);
                }
                catch (Exception e)
                {
                    WriteToOutput("Could not delete temp submit file: " + e.Message);
                }
            }
        }
        #endregion

        #region Private Members
        private void UploadToServer()
        {
            _response = HttpUpload.Upload(
                _uri,
                _requestParams,
                null,
                null,
                _debugOut,
                _uploadSpec
            );
        }

        private Properties ReadProperties(string solutionDir, string fileName, string[] requiredKeys)
        {
            string propFileName = solutionDir + Path.DirectorySeparatorChar + fileName;
            Properties props = new Properties();
            try
            {
                props.Read(propFileName);
                return VerifyProperties(props, requiredKeys, fileName) ? props : null;
            }
            catch (Exception e)
            {
                Dialogs.ShowErrorDialog(ERROR_DIALOG_TITLE, "Error reading " + fileName + " file", e);
                return null;
            }
        }

        private bool VerifyProperties(Properties properties, string[] requiredKeys, string fileName)
        {
            foreach (string key in requiredKeys)
            {
                if (properties.Get(key) == null)
                {
                    Dialogs.ShowErrorDialog(ERROR_DIALOG_TITLE, "File " + fileName + " is missing required key " + key);
                    return false;
                }
            }
            return true;
        }

        private void ExportToTempFile(string tempFileName)
        {
            FileInfo fileInfo = new FileInfo(tempFileName);
            fileInfo.Attributes = FileAttributes.Temporary;

            FileStream outputStream = File.OpenWrite(tempFileName);
            SetOutputStream(outputStream);

            // Export to the temp file, ensuring that output stream is closed
            using (this)
            {
                Export();
            }
        }
        #endregion
    }
}
